package com.zeratul.common.i18n.source.nacos.base;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.zeratul.common.i18n.constants.I18nConstants;
import com.zeratul.common.i18n.source.nacos.JsonNacosResourceMessageSource;
import com.zeratul.common.i18n.source.nacos.PropNacosResourceMessageSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.AbstractResourceBasedMessageSource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;

/**
 * 从nacos上获取国际化信息的 消息源
 */
@Slf4j
@Data
public abstract class NacosResourceMessageSource extends AbstractResourceBasedMessageSource {


    /**
     * 本地缓存集合
     * key - String 配置文件名称
     * value - Properties 配置文件属性对象
     */
    private Map<String, BaseHolder> fileNameCacheMap = new ConcurrentHashMap<>();

    /**
     * Nacos配置管理对象
     */
    private NacosConfigManager nacosConfigManager;

    /**
     * 通用基础的配置文件组名
     */
    private String baseGroupName = I18nConstants.BASE_GROUP_NAME;

    /**
     * 组的名称集合
     */
    private List<String> groupNames = new CopyOnWriteArrayList<>();

    {
        //将基础组名加入集合
        groupNames.add(baseGroupName);
    }

    /**
     * 添加组的名称，越靠前的组，优先级越高（同名配置，返回优先级高的值）
     * @param groupName
     */
    public void addGroupName(String groupName) {
        //保证所有的组名都排在base基础组名的前面，base的优先级最低
        int index = groupNames.size() - 1;
        if (index < 0) {
            index = 0;
        }
        groupNames.add(index, groupName);
    }


    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        //获取当前语言环境
        locale = Optional.ofNullable(locale).orElse(LocaleContextHolder.getLocale());
        //根据环境读取配置文件名称
        List<String> fileNames = this.getFileNames(locale);
        if (CollectionUtils.isEmpty(fileNames)) {
            return null;
        }

        //循环配置文件名称
        for (String fileName : fileNames) {
            //根据配置文件名称，去nacos上读取配置
            BaseHolder propHolder = this.loadProperties(fileName);
            if (Objects.isNull(propHolder)) {
                continue;
            }
            //从配置对象中读取对应的属性，并且返回value（国际化结果）
            String value = propHolder.getProperty(code, locale);
            if (StringUtils.isEmpty(value)) {
                continue;
            }
            //封装并且返回
            return createMessageFormat(value, locale);
        }
        return null;
    }

    /**
     * 获取nacos配置中心国际化数据源的实例
     * @return
     */
    public static NacosResourceMessageSource getInstance(String configFormat){
        switch (configFormat) {
            case "json":
                return new JsonNacosResourceMessageSource();
            case "properties":
                return new PropNacosResourceMessageSource();
            default:
                log.error("【基于Nacos加载国际化配置】数据格式不合法:{}", configFormat);
                return null;
        }
    }

    /**
     * 根据文件从nacos中加载多语言配置文件
     * @param fileName - 国际化文件名称，比如message、message_zh_CN
     * @return 配置类对象
     * @throws IOException
     * @throws NacosException
     */
    protected BaseHolder loadProperties(String fileName) {

        //从缓存中获取配置
        BaseHolder baseHolder =  this.getFileNameCacheMap().get(fileName);
        if (!Objects.isNull(baseHolder)) {
            return baseHolder;
        }

        synchronized (fileName.intern()) {
            //双重判定，避免并发读取Nacos
            baseHolder = this.getFileNameCacheMap().get(fileName);
            if (!Objects.isNull(baseHolder)) {
                return baseHolder;
            }

            //创建一个配置信息的包装对象，具体对象由子类决定
            baseHolder = createBaseHolder();
            //读取Nacos文件
            for (String groupName : getGroupNames()) {
                String config = this.loadNacosConfig(fileName, groupName, baseHolder);
                //将properties对象加入到包装对象中，和组名进行对应
                baseHolder.addGroupProp(groupName, config);
            }

            //放入缓存，有可能放入空对象
            this.getFileNameCacheMap().put(fileName, baseHolder);
        }
        return baseHolder;
    }

    /**
     * 读取nacos配置文件
     * @param dataId
     * @param groupName
     * @return
     */
    protected String loadNacosConfig(String dataId, String groupName, BaseHolder baseHolder) {
        try {
            //读取Nacos配置中心的配置数据
            String config = getNacosConfigManager().getConfigService().getConfigAndSignListener(dataId + "." + baseHolder.getSuffix(), groupName, 5000, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    //调用配置更新方法
                    configUpdate(configInfo, dataId, groupName);
                }
                @Override
                public Executor getExecutor() {
                    return null;
                }
            });
            log.debug("【国际化-基于Nacos配置】成功读取Nacos配置信息，文件名称：{} 组名称：{}", dataId, groupName);
            return config;
        } catch (NacosException e) {
            log.error("【国际化-基于Nacos配置】读取Nacos配置信息失败，文件名称：{}, 组名：{}", dataId, groupName, e);
        }
        return null;
    }

    /**
     * nacos配置信息更新
     */
    protected void configUpdate(String config, String dataId, String groupName){
        log.debug("【国际化-基于Nacos配置】更新Nacos配置信息，文件名称：{} 组名：{}", dataId, groupName);
        BaseHolder baseHolder = this.getFileNameCacheMap().get(dataId);
        if (Objects.nonNull(baseHolder)) {
            baseHolder.addGroupProp(groupName, config);
        }
    }

    /**
     * 返回所有的配置文件名称
     * @return
     */
    protected abstract List<String> getFileNames(Locale locale);

    /**
     * 创建BaseHolder对象 - 由子类决定创建什么对象
     * @return
     */
    protected abstract BaseHolder createBaseHolder();

    /**
     * 初始化方法
     */
    protected void init() {};



    /**
     * 国际化配置的包装对象
     *
     * groupNames -> 管理所有配置的GroupName，比如 base/微服务名称等，
     *      这里用List管理主要是为了保证组的优先级，靠前的包，对应的配置优先级更高
     * groupDatas -> 管理国际化配置对象，key表示组名，value是一个配置对象
     *     （这里是一个抽象设计，具体的配置对象 取决于不同子类，因为nacos有多种管理配置的格式，暂时支持 json、properties）
     *
     */
    @Data
    public static abstract class BaseHolder<T> {
        //对应的组集合
        private Map<String, T> groupDatas = new ConcurrentHashMap<>();
        //添加的组名，将组名按顺序管理起来，保证前面的组名，优先级越高
        private List<String> groupNames = new CopyOnWriteArrayList<>();

        /**
         * 将组名 和 国际化配置信息 加入当前Holder对象中
         * @param groupName
         * @param configInfo
         */
        public void addGroupProp(String groupName, String configInfo) {
            if(!StringUtils.hasLength(configInfo) || !StringUtils.hasLength(groupName)) {
                return;
            }
            //将配置字符串转换成T对象 具体对象由子类决定
            T t = convertConfig(configInfo);
            if (Objects.isNull(t)) {
                return;
            }
            this.getGroupDatas().put(groupName, t);
            if (this.getGroupNames().contains(groupName)) {
                return;
            }
            this.groupNames.add(groupName);
        }

        /**
         * 读取配置对应的value
         * @param key
         * @return
         */
        public String getProperty(String key, Locale locale) {
            //循环管理的所有组配置
            for (String groupName : this.getGroupNames()) {
                //根据组名称获取到对应的配置对象
                T t = this.getGroupDatas().get(groupName);
                if (Objects.isNull(t)) {
                    continue;
                }
                //从配置对象中获取相关的国际化属性值
                String value = this.getValue(t, locale, key);
                if (!StringUtils.hasLength(value)) {
                    continue;
                }
                return value;
            }
            return null;
        }

        /**
         * 返回配置后缀
         * @return
         */
        protected abstract String getSuffix();

        /**
         * 转换方法，将配置信息，转换成相应的对象（具体对象由子类决定）
         * @param config
         * @return
         */
        public abstract T convertConfig(String config);

        /**
         * 从Holder对象中，根据语言 和 code 获取国际化value
         * @param t - 国际化配置对象 - 保证非空
         * @param key - 国际化code
         * @return
         */
        public abstract String getValue(T t, Locale locale, String key);
    }
}
